#include "sequencerunner.h"
#include <QAbstractListModel>
#include <QFileInfo>
#include <QVariant>

#include "modelViewRules.h"
#include "supported_files.h"
#include "testableAssert.h"

SequenceRunner::SequenceRunner(const QAbstractListModel &sequenceModel,
                               ITimerService *timer, QObject *parent) :
   IF_SequenceEngineInterface(parent),
   m_isPlaying(false),
   m_sequenceModel(sequenceModel),
   m_timer(timer),
   m_timerlistener(this),
   m_indexOfArmed(-1),
   m_indexOfPlaying(-1)
{
   connect( &m_timerlistener, SIGNAL(timeout()), this, SLOT(onReadyToPlayNext()) );
}

/**
 * @brief start the sequence. Armed event is fired immediately,
 *        while next event will be fired after the specified delay
 *        from its activation.
 */
void SequenceRunner::play()
{
   if (armedItemIsValid())
   {
      const SequenceItem *item = m_sequenceModel.index( m_indexOfArmed).
                                 data( modelViewRules::Binary).value<const SequenceItem *>();
      T_ASSERT( item != NULL);

      m_isPlaying = true;
      m_indexOfPlaying = m_indexOfArmed;

      requestActivation( item->type(), item->label());
      emit activateRow( m_indexOfArmed);
      emit sequenceIsRunning(true);

      /* start timer for next event, if any */
      updateArmedIndex();
      armNextTimeout();
   }
   else
   {
      m_isPlaying = false;
      emit sequenceIsOver();
   }
}

/**
 * called whenever the sequence must trigger a new event
 * @param type - see type definition for details
 * @param label - string that identifies an event, given its type.
 */
void SequenceRunner::requestActivation(SequenceItem::eventType type, QString label)
{
   if (type == SequenceItem::TRACK)
   {
      /* a picture is distinguished by other media by file extention */
      QFileInfo labelInfo( label);
      if (PICTURE_SUPPORTED_FORMATS.contains( labelInfo.suffix(), Qt::CaseInsensitive))
      {
         emit activatePicture( label, true);
      }
      else
      {
         emit activateMedia( label, true);
      }
   }
   else if (type == SequenceItem::LIGHT)
   {
      emit activateLight( label, true);
   }
   else
   {
      emit sequenceError(tr("Invalid event type"));
   }
}

/**
 * change m_indexOfArmed according to list and info in current item
 */
void SequenceRunner::updateArmedIndex()
{
   const SequenceItem *activeItem = m_sequenceModel.index( m_indexOfArmed).
                                    data( modelViewRules::Binary).value<const SequenceItem *>();
   T_ASSERT( activeItem != NULL);

   int nextItem = activeItem->indexOfNext();

   if (nextItem == SequenceItem::NEXT_IS_UNDEFINED)
   {
      /* arm next item, it one is there ... */
      bool nextItemAvailable = (m_indexOfArmed < (m_sequenceModel.rowCount() -1));
      m_indexOfArmed = (nextItemAvailable) ? (m_indexOfArmed + 1) : -1;
   }
   else
   {
      m_indexOfArmed = nextItem;
   }
}

void SequenceRunner::armNextTimeout()
{
   if (armedItemIsValid())
   {
      const SequenceItem *nextItem = m_sequenceModel.index( m_indexOfArmed).
                                     data( modelViewRules::Binary).value<const SequenceItem *>();

      if (nextItem->delayMs() > 0)
      {
         m_timer->triggerTimeout( nextItem->delayMs(), &m_timerlistener);
      }

      emit timeToNextEvent( nextItem->delayMs());
   }
}

bool SequenceRunner::armedItemIsValid()
{
   return (m_indexOfArmed >= 0) && (m_indexOfArmed < m_sequenceModel.rowCount());
}

/**
 * stop executing sequence events, and signal this with an event.
 * On next play, current playing item will be retriggered, if not changed.
 */
void SequenceRunner::pause()
{
   m_isPlaying = false;
   m_timer->stopTimer();
   emit sequenceIsRunning(false);

   m_indexOfArmed = m_indexOfPlaying;
   m_indexOfPlaying = -1;
}

bool SequenceRunner::isPlaying()
{
   return m_isPlaying;
}

void SequenceRunner::selectIndex(const QModelIndex &index)
{
   selectIndex( index.row());
}

void SequenceRunner::selectIndex(int rowIndex)
{
   if (rowIndex != m_indexOfPlaying)
   {
      pause();
   }

   /* 'rowIndex' may be -1 if active item is deleted */
   T_ASSERT( rowIndex <= m_sequenceModel.rowCount());

   m_indexOfArmed = rowIndex;
   /* do not emit change notification because
    * active item has been changed by outside */
}

void SequenceRunner::onReadyToPlayNext()
{
   play();
}

